
package com.huaweicloud.haydn.delivercore.agent.service.metadata.impl;

import static com.huaweicloud.haydn.delivercore.agent.config.Constants.DOT_FILETYPE_CSV;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.FILETYPE_ZIP;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.SYS_URL;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_DB;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_EMPTY;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_FIELD;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_TABLE;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.huaweicloud.haydn.delivercore.agent.common.exception.BusinessErrorEnum;
import com.huaweicloud.haydn.delivercore.agent.common.exception.BusinessException;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.DbExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.FieldEmptyExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.FieldExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.TableExcelDto;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.DatabaseUtil;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @since 2022-09-26
 */
@Service
@Slf4j
public class AgentExecUtil {

    public void genDbExcelDto(DatabaseUtil dbUtil, String datalinkName, String dbNames, String schemaNames,
        String idx) {
        SimpleDateFormat ymd = new SimpleDateFormat("yyyyMMdd");
        String today = ymd.format(new Date());
        List<DbExcelDto> allDbs = dbUtil.getAllDbs(datalinkName, dbNames, schemaNames);
        allDbs.forEach(dto -> dto.setGenTime(today));
        HashSet<DbExcelDto> dbExcelDtos = new HashSet<>(allDbs);
        String fileName = idx + "_" + TYPE_DB + "_" + ymd.format(new Date()) + DOT_FILETYPE_CSV;
        ExcelWriter excelWriter = EasyExcel.write(fileName, DbExcelDto.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();
        excelWriter.write(dbExcelDtos, writeSheet);
        excelWriter.finish();
        execZip(datalinkName, fileName);
    }

    public void genTableExcelDto(DatabaseUtil dbUtil, String datalinkName, String dbNames, String schemaNames,
        String tableNames, String idx) {
        SimpleDateFormat ymd = new SimpleDateFormat("yyyyMMdd");
        String today = ymd.format(new Date());
        List<TableExcelDto> allTables = dbUtil.getAllTables(datalinkName, dbNames, schemaNames, tableNames);
        allTables.forEach(dto -> dto.setGenTime(today));
        HashSet<TableExcelDto> tableExcelDtos = new HashSet<>(allTables);
        String fileName = idx + "_" + TYPE_TABLE + "_" + ymd.format(new Date()) + DOT_FILETYPE_CSV;
        ExcelWriter excelWriter = EasyExcel.write(fileName, TableExcelDto.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();
        excelWriter.write(tableExcelDtos, writeSheet);
        excelWriter.finish();
        execZip(datalinkName, fileName);
    }

    public void genFieldExcelDto(DatabaseUtil dbUtil, String datalinkName, String dbNames, String schemaNames,
        String tableNames, String idx) {
        SimpleDateFormat ymd = new SimpleDateFormat("yyyyMMdd");
        String today = ymd.format(new Date());
        List<FieldExcelDto> allFields = dbUtil.getAllFields(datalinkName, dbNames, schemaNames, tableNames);
        List<String> pkFieldList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(allFields)) {
            log.info(JSON.toJSONString(allFields));
        } else {
            log.info("empty allFields");
        }
        allFields.forEach(dto -> {
            dto.setGenTime(today);
            if ("YES".equals(dto.getIsPrimaryKey())) {
                pkFieldList.add(dto.getDatalinkName() + dto.getDatabaseName()
                    + Optional.ofNullable(dto.getSchemaName()).orElse("") + dto.getTableName() + dto.getFieldNameEn());
            }
        });
        HashSet<FieldExcelDto> fieldExcelDtos = new HashSet<>(allFields);
        fieldExcelDtos.removeIf(dto -> pkFieldList
            .contains(dto.getDatalinkName() + dto.getDatabaseName()
                + Optional.ofNullable(dto.getSchemaName()).orElse("") + dto.getTableName() + dto.getFieldNameEn())
            && !"YES".equals(dto.getIsPrimaryKey()));
        String fileName = idx + "_" + TYPE_FIELD + "_" + ymd.format(new Date()) + DOT_FILETYPE_CSV;
        ExcelWriter excelWriter = EasyExcel.write(fileName, FieldExcelDto.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();
        excelWriter.write(fieldExcelDtos, writeSheet);
        excelWriter.finish();
        execZip(datalinkName, fileName);
    }

    public void genEmptyRateExcelDto(DatabaseUtil dbUtil, String datalinkName, String dbNames, String schemaNames,
        String tableNames, String idx, Map<String, List<String>> fieldTableMap) {
        SimpleDateFormat ymd = new SimpleDateFormat("yyyyMMdd");
        List<FieldEmptyExcelDto> allEmptyRateFields =
            dbUtil.getAllEmptyRateFields(datalinkName, dbNames, schemaNames, fieldTableMap);
        HashSet<FieldEmptyExcelDto> fieldEmptyExcelDtos = new HashSet<>(allEmptyRateFields);
        String fileName = idx + "_" + TYPE_EMPTY + "_" + ymd.format(new Date()) + DOT_FILETYPE_CSV;
        ExcelWriter excelWriter = EasyExcel.write(fileName, FieldEmptyExcelDto.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();
        excelWriter.write(fieldEmptyExcelDtos, writeSheet);
        excelWriter.finish();
        execZip(datalinkName, fileName);
    }

    private void execZip(String datalinkName, String fileName) {
        log.info(datalinkName);
        ZipFile zip = null;
        ZipOutputStream zipOut = null;
        File file = null;
        File csvFile = null;
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            file = new File(System.getProperty(SYS_URL), "result." + FILETYPE_ZIP);
            if (!validatePath(file.getCanonicalPath())) {
                throw new BusinessException(BusinessErrorEnum.BASE_BAD_REQUEST);
            }
            log.info("excel dir: " + System.getProperty(SYS_URL) + File.separator + "result." + FILETYPE_ZIP);
            if (!file.exists()) {
                boolean newFile = file.createNewFile();
                if (!newFile) {
                    throw new IOException();
                }
                fileOutputStream = new FileOutputStream(file);
                if (!validatePath(file.getCanonicalPath())) {
                    throw new BusinessException(BusinessErrorEnum.BASE_BAD_REQUEST);
                }
                zipOut = new ZipOutputStream(fileOutputStream);
                ZipEntry zipEntry = new ZipEntry(fileName);
                zipOut.putNextEntry(zipEntry);
                fileInputStream = new FileInputStream(System.getProperty(SYS_URL) + File.separator + fileName);
                IOUtils.copy(fileInputStream, zipOut);
                zipOut.closeEntry();
            } else {
                zip = new net.lingala.zip4j.ZipFile(file);
                ZipParameters parameters = new ZipParameters();
                zip.addFile(new File(System.getProperty(SYS_URL) + File.separator + fileName), parameters);
                removeOutdateFile(zip);
            }
            csvFile = new File(System.getProperty(SYS_URL) + File.separator + fileName);
        } catch (IOException ioe) {
            log.error(ioe.getMessage());
            log.error("zip error");
        } finally {
            close(zip, zipOut, fileInputStream, csvFile, fileOutputStream);
        }
    }

    private boolean validatePath(String path) {
        return path.startsWith(System.getProperty(SYS_URL));
    }

    @SneakyThrows
    private void removeOutdateFile(ZipFile zip) {
        SimpleDateFormat ymd = new SimpleDateFormat("yyyyMMdd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DATE, -30);
        Date time = calendar.getTime();
        String timeStr = ymd.format(time);
        String dbName = TYPE_DB + "_" + timeStr + DOT_FILETYPE_CSV;
        String tableName = TYPE_TABLE + "_" + timeStr + DOT_FILETYPE_CSV;
        String fieldName = TYPE_FIELD + "_" + timeStr + DOT_FILETYPE_CSV;
        List<FileHeader> fileHeaders = zip.getFileHeaders();
        for (FileHeader fileHeader : fileHeaders) {
            if (dbName.equals(fileHeader.getFileName()) || tableName.equals(fileHeader.getFileName())
                || fieldName.equals(fileHeader.getFileName())) {
                zip.removeFile(fileHeader);
            }
        }
    }

    public void close(ZipFile zip, ZipOutputStream zipOut, FileInputStream fileInputStream, File csvFile,
        FileOutputStream fileOutputStream) {
        if (zip != null) {
            try {
                zip.close();
            } catch (IOException e) {
                log.error("Close zipOut failed!");
                log.error(e.getMessage(), e);
            }
        }

        if (zipOut != null) {
            try {
                zipOut.close();
            } catch (IOException e) {
                log.error("Close zipOut failed!");
                log.error(e.getMessage(), e);
            }
        }
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                log.error("Close zipOut failed!");
                log.error(e.getMessage(), e);
            }
        }
        if (fileOutputStream != null) {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                log.error("Close zipOut failed!");
                log.error(e.getMessage(), e);
            }
        }
        FileUtils.deleteQuietly(csvFile);
    }

}
